/*
 * $Id$
 */

package corejs.rt;

import java.math.BigDecimal;

/**
 *
 * @author pengyu
 */
public class Str extends Primitive implements ArrayAccessSupport, Comparable<Str>  {

   // -- String.prototype ------
   public static final Value prototype = new Obj(Obj.prototype);
      
   static void _str_init_prototype_() {
      // trim
      prototype.store("trim", new Invocable() {
         @Override
         public Value invoke(final ExecutionContext ec) {
            return ec.getStr("this").trim();
         }
      });   
     
      // toString
      prototype.store("toString", new Invocable() {
         @Override
         public Value invoke(final ExecutionContext ec) {
            return ec.get();
         }
      }); 
      
      // valueOf
      prototype.store("valueOf", new Invocable() {
         @Override
         public Value invoke(final ExecutionContext ec) {
            return ec.get();
         }
      });

      // toUpperCase
      prototype.store("toUpperCase", new Invocable() {
         @Override
         public Value invoke(final ExecutionContext ec) {
            return ec.getStr("this").toUpperCase();
         }
      });

      // toLowerCase
      prototype.store("toLowerCase", new Invocable() {
         @Override
         public Value invoke(final ExecutionContext ec) {
            return ec.getStr("this").toLowerCase();
         }
      });

      // substr
      final String[] _substr_arg_names_ = {"!start", "!length"};
      prototype.store("substr", new Invocable() {
         @Override
         public String[] getArgumentNames() {return _substr_arg_names_;}
         
         @Override
         public Value invoke(final ExecutionContext ec) {
            final Str s = ec.getStr("this");
            return s.substr(ec.getNumber("!start").intValue(),
                            ec.getNumber("!length", s.length()).intValue());
         }
      });
   };
   
   // -- constants
   public static final Str UNDEFINED  = new Str("undefined");
   public static final Str BOOLEAN    = new Str("boolean");
   public static final Str OBJECT     = new Str("object");
   public static final Str NUMBER     = new Str("number");
   public static final Str FUNCTION   = new Str("function");
   public static final Str STRING     = new Str("string");
   
   public static final Str NULL       = new Str("null");   
   public static final Str NaN        = new Str("NaN");   
   public static final Str TRUE       = new Str("true");   
   public static final Str FALSE      = new Str("false");

   public static final Str EMPTY      = new Str();

   // -- instance variables
   private String _str;

   // -- constructors
   public Str() {this("");  }

   public Str(final Object o) { this(o.toString()); }
   
   public Str(final char ch) { this(String.valueOf(ch)); }
   
   public Str(final int i) { this(String.valueOf(i)); }
   
   public Str(final long l) { this(String.valueOf(l)); }
   
   public Str(final String s) {
     super(prototype);
     _str = (null == s ? "" : s);
     store("length", _str.length());
   }

   // -- instance methods
   @Override
   public String toString() { return _str; }

   // spec 11.9.3
   @Override
   public boolean equals(final Primitive y) {
      return this == y ||  (y.isStr() ? equals(y.toStr()) : y.equals(this));
   }

   @Override
   public boolean equalsStrictly(final Value y) {
      return this == y || (y.isStr() && equals(y.cast(Str.class)));
   }
  
   /**
    *
    * @param other
    * @return
    */
   private boolean equals(final Str other) {
      return isEmpty() ? other.isEmpty() : _str.equals(other._str);
   }

   /**
    *
    * @param times
    * @return
    */
   public Str multiply(final Number times) {
      final StringBuilder sb = new StringBuilder();
      for (int i = 0; i < times.longValue(); ++i) {
         sb.append(_str);
      }
      return new Str(sb);
   }

   @Override
   public Value resolve(final int index) {
      return checkIndex(index) ?
         new Str(_str.charAt(index < 0 ? _str.length() + index : index))
         : Undefined.UNDEFINED;
   }
   
   private boolean checkIndex(final int index) {
      if (isEmpty()) {
         return false;
      }
      final int i = _str.length(), j = i + index;
      return j >= 0 && j < i * 2;
   }
   
   @Override
   public Bool toBool() {
      return isEmpty() ? Bool.FALSE : Bool.TRUE;
   }
   
   @Override
   public Str toStr() {return this;}
   
   // spec 9.3.1
   @Override
   public Number toNumber() {
      try {
         return Int.valueOf(Integer.parseInt(_str));
      }
      catch (final NumberFormatException e1) {
         try {
            return new Decimal(new BigDecimal(_str));
         }
         catch (final NumberFormatException e2) {
            return Number.NaN;
         }
      }
   }

   public Str concat(final Str other) {
      return new Str(_str + other._str);
   }
   
   /**
    * 
    * @param other
    * @return 
    */
   @Override
   public int compareTo(final Str other) {
      return _str.compareTo(other._str);
   }
   
   // -- typeof ------
   @Override
   public Str typeof() {return STRING;}
   
   /**
    *
    * @return
    */
   public Str trim() {
      return isEmpty() ? this : new Str(_str.trim());
   }

   /**
    * 
    * @return 
    */
   public Str toUpperCase() {
      return isEmpty() ? this : new Str(_str.toUpperCase());
   }
   
   /**
    * 
    * @return 
    */
   public Str toLowerCase() {
      return isEmpty() ? this : new Str(_str.toLowerCase());
   }

   /**
    * 
    * @param start
    * @param length
    * @return 
    */
   public Str substr(final int start, final int length) {
      final int len = length();
      if (0 == len || start >= len || 0 >= length) {
         return EMPTY;
      }
      
      int i = (start >= 0 ? start : len + start);
      if (i < 0) { i = 0; }
      int j = i + length; j = (j >= len ? len : j);

      return new Str(_str.substring(i, j));
   }

   public int length() {
      return null == _str ? 0 : _str.length();
   }
   
   /**
    *
    * @return
    */
   @Override
   public boolean isEmpty() { return 0 == length(); }
   

   // -- String constructor ----
   public static final Invocable _constructor_ = new Invocable() {
      {
         store("prototype", Str.prototype);
      }
      
      @Override
      public Value invoke(final ExecutionContext ec) {
         throw new UnsupportedOperationException("Not supported yet."); 
      }
   };
}
